
#include "stdafx.h"

#include "demos.h"

#include "tesswave.h"
#include "CImg.h"

#define SQR(x) ((x)*(x))
TessWaveMesh::TessWaveMesh(const char* nTex, float dt, float dh, float c)
    :m_dt(dt)
    ,m_dh(dh)
    ,m_c(c)
    ,m_currentFrame(0)
{
    GLuint shader;
    m_flowProg = glCreateProgram();
    m_filtProg = glCreateProgram();
    shader = glCreateShader(GL_VERTEX_SHADER);
    if(!CompileAndPrintLog(shader, "shaders/wave.vs.glsl", NULL))
        throw "Errors in compilation";
    glAttachShader(m_flowProg, shader);
    glAttachShader(m_filtProg, shader);
    glDeleteShader(shader);
    shader = glCreateShader(GL_FRAGMENT_SHADER);
    if(!CompileAndPrintLog(shader, "shaders/wave.fs.glsl", NULL))
        throw "Errors in compilation";
    glAttachShader(m_flowProg, shader);
    glDeleteShader(shader);
    if(!LinkAndPrintLog(m_flowProg))
        throw "Errors in link";
    m_wave_t = glGetUniformLocation(m_flowProg, "uWave");
    m_wave_prev_t = glGetUniformLocation(m_flowProg, "uPrevWave");
    m_lambda = glGetUniformLocation(m_flowProg, "uWaveStepConst");
    m_flowPosition = glGetAttribLocation(m_flowProg, "Position");

    shader = glCreateShader(GL_FRAGMENT_SHADER);
    if(!CompileAndPrintLog(shader, "shaders/wave_filt.fs.glsl", NULL))
        throw "Errors in compilation";
    glAttachShader(m_filtProg, shader);
    glDeleteShader(shader);
    if(!LinkAndPrintLog(m_filtProg))
        throw "Errors in link";
    m_filtWave = glGetUniformLocation(m_filtProg, "uWave");
    m_filtDir = glGetUniformLocation(m_filtProg, "uDir");
    m_filtConst = glGetUniformLocation(m_filtProg, "uFiltConst");
    m_filtPos = glGetAttribLocation(m_filtProg, "Position");

    m_drawProg = glCreateProgram();
    shader = glCreateShader(GL_VERTEX_SHADER);
    if(!CompileAndPrintLog(shader, "shaders/tess.vs.glsl", NULL, 1, "shaders/matrices.h.glsl"))
        throw "Errors in compilation";
    glAttachShader(m_drawProg, shader);
    glDeleteShader(shader);
    shader = glCreateShader(GL_TESS_CONTROL_SHADER);
    if(!CompileAndPrintLog(shader, "shaders/tess.tc.glsl", NULL, 1, "shaders/matrices.h.glsl"))
        throw "Errors in compilation";
    glAttachShader(m_drawProg, shader);
    glDeleteShader(shader);
    shader = glCreateShader(GL_TESS_EVALUATION_SHADER);
    if(!CompileAndPrintLog(shader, "shaders/tess.te.glsl", NULL, 1, "shaders/matrices.h.glsl"))
        throw "Errors in compilation";
    glAttachShader(m_drawProg, shader);
    glDeleteShader(shader);
    shader = glCreateShader(GL_FRAGMENT_SHADER);
    char preamb[50];
    sprintf_s(preamb, 50, "#define MAX_LIGHTS %d\n", MAX_LIGHTS);
    if(!CompileAndPrintLog(shader, "shaders/tess.fs.glsl", preamb, 1, "shaders/lighting.h.glsl"))
        throw "Errors in compilation";
    glAttachShader(m_drawProg, shader);
    glDeleteShader(shader);
    if(!LinkAndPrintLog(m_drawProg))
        throw "Errors in link";
    m_drawPosition = glGetAttribLocation(m_drawProg, "Position");
    m_drawWave = glGetUniformLocation(m_drawProg, "uWave");
    m_lambda = glGetUniformLocation(m_drawProg, "uScale");

    FILE *vals;
    fopen_s(&vals, nTex, "rb");
    if(!vals)
        throw "Can't open texture";

    fread(&m_waveWidth, sizeof(m_waveWidth), 1, vals);
    fread(&m_waveHeight, sizeof(m_waveHeight), 1, vals);

    float* nArr=new float[m_waveHeight*m_waveWidth];
    float* zeros=new float[m_waveHeight*m_waveWidth];

    fread(nArr, sizeof(float), m_waveWidth*m_waveHeight, vals);
    memset(zeros, 0, sizeof(float)*m_waveHeight*m_waveWidth);

    glGenFramebuffers(1, &m_fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
    float* imgs[4] = {zeros,zeros,NULL,NULL};
    glGenTextures(4, m_waves);
    glActiveTexture(GL_TEXTURE0);
    for(int i=0;i<4;i++) {
        glBindTexture(GL_TEXTURE_RECTANGLE, m_waves[i]);
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
        glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_R32F, m_waveWidth, m_waveHeight, 0, GL_RED, GL_FLOAT, imgs[i]);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+i, GL_TEXTURE_RECTANGLE, m_waves[i], 0);
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    using namespace cimg_library;
    CImg<float> n(nArr, m_waveWidth, m_waveHeight);
    n.mirror('y');
    CImg<float> ns(n);
    const int pad=3;
    n.sqr()*=SQR(c*dt/dh);  // Compute Courant number
    ns.distance(1.0f, 0);   // Distance transform with L_inf metric
    ns.erode(pad,pad,1);    // Eliminate whatever is less than 'pad'
    ns.norm();              // leave zeros or ones
    ns*=1/(1+2*exp(-0.5f/SQR(.9f)));    // Central Gaussian term

    glGenTextures(1, &m_filtTex);
    glBindTexture(GL_TEXTURE_RECTANGLE, m_filtTex);
    glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
    glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
    glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_R32F, m_waveWidth, m_waveHeight, 0, GL_RED, GL_FLOAT, ns.data());

    glGenTextures(1, &m_lambdaTex);
    glBindTexture(GL_TEXTURE_RECTANGLE, m_lambdaTex);
    glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
    glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
    glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_R32F, m_waveWidth, m_waveHeight, 0, GL_RED, GL_FLOAT, n.data());

    glGenTextures(1, &m_wavePresent);
    glBindTexture(GL_TEXTURE_2D, m_wavePresent);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, m_waveWidth, m_waveHeight, 0, GL_RED, GL_FLOAT, NULL);

    delete[] nArr;
    delete[] zeros;
}

TessWaveMesh::~TessWaveMesh()
{
    glBindTexture(GL_TEXTURE_RECTANGLE, 0);
    glBindTexture(GL_TEXTURE_2D, 0);
    glDeleteTextures(sizeof(m_waves)/sizeof(GLuint), m_waves);
    glDeleteTextures(1, &m_wavePresent);
    glDeleteTextures(1, &m_lambdaTex);
    glDeleteTextures(1, &m_filtTex);
    glDeleteFramebuffers(1, &m_fbo);
    glDeleteProgram(m_drawProg);
    glDeleteProgram(m_flowProg);
    glDeleteProgram(m_filtProg);
}

void TessWaveMesh::AttachUniforms(const UniformMap& uBlocks)
{
    m_uniformModel=static_cast<const ModelBlock*>(uBlocks.at(MODEL_BLOCK));
    m_uniformModel->BindToProgram(m_drawProg);
    uBlocks.at(LIGHTS_BLOCK)->BindToProgram(m_drawProg);
    uBlocks.at(MATRICES_BLOCK)->BindToProgram(m_drawProg);
}

void TessWaveMesh::Draw(GLsizei width, GLsizei height)
{
    GLenum drawBuf = GL_COLOR_ATTACHMENT0+(m_currentFrame + 2)%3;
    GLuint currentWave=(m_currentFrame + 1)%3;

    // filter
    glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
    glViewport(0, 0, m_waveWidth, m_waveHeight);
    glDrawBuffer(GL_COLOR_ATTACHMENT3);
    glUseProgram(m_filtProg);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_RECTANGLE, m_waves[currentWave]);
    glUniform1i(m_filtWave, 0);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_RECTANGLE, m_filtTex);
    glUniform1i(m_filtTex, 1);
    glUniform2i(m_filtDir, 1, 0);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    // now across rows
    glUniform2i(m_filtDir, 0, 1);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_RECTANGLE, m_waves[3]);    // assign to first texture the output
    glDrawBuffer(drawBuf);                              // and this will be the toned down wave (back in its place)
    glDrawArrays(GL_TRIANGLES, 0, 6);
    // copy the wave to texture2d
    glReadBuffer(drawBuf);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, m_wavePresent);
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_waveWidth, m_waveHeight);
    glGenerateMipmap(GL_TEXTURE_2D);    // This will be a performance hog. Do I need it?

    // draw
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glViewport(0, 0, width, height);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glUseProgram(m_drawProg);
    //...

    // end draw

    // wave flow
    glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
    glViewport(0, 0, m_waveWidth, m_waveHeight);
    glDrawBuffer(drawBuf);
    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(m_flowProg);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_RECTANGLE, m_waves[currentWave]);
    glUniform1i(m_wave_t, 0);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_RECTANGLE, m_waves[m_currentFrame]);
    glUniform1i(m_wave_prev_t, 1);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_RECTANGLE, m_lambdaTex);
    glUniform1i(m_lambda, 2);
    glDrawArrays(GL_TRIANGLES, 0, 6);

    // restore viewport
    glViewport(0, 0, width, height);
    m_currentFrame++;
}
